# @Time : 2020/12/23 16:27
# @Author : Fioman 
# @Phone : 13149920693
import socket
import threading

import gevent

from settings import *
from hd_socket.parse_xml import *


class HdClient(object):
    def __init__(self, js):
        # 先获取设备的服务器的IP地址,还有就是端口,还有就是操作模式,还有就是askDuration,datetimeDuration,
        self.js = js
        self.timeout = 3
        self.msgList = []  # 这个消息列表只存放10条,无论来了多少条,只会显示10条.将这个列表发送给前端.
        res = self.get_eap_params()
        if res == []:
            self.deviceName = "HD_CBJ"
            self.eapIp = "192.168.0.100"
            self.deviceIp = "192.168.0.1"
            self.port = 5000
            self.operationMode = "0"
            self.askDuration = 60
            self.currentTimeDuration = 60
            self.totalCountDuration = 0

        self.recvMsgThread = threading.Thread(target=self.recv_msg_from_eap, name="recv_msg_thread")
        self.sendMsgThread = threading.Thread(target=self.send_msg_to_eap, name="send_msg_thread")
        self.askRequestThread = threading.Thread(target=self.ask_eap_there_request, name="ask_request_thread")
        self.datetimeRequestThread = threading.Thread(target=self.date_time_report, name="date_time_report")
        self.totalCountReportThread = threading.Thread(target=self.total_count_report, name='total_count_report')
        self.managerThread = threading.Thread(target=self.manager, name="manager_thread")


    def manager(self):
        while True and IS_MAIN_ALIVE:
            try:
                if not self.recvMsgThread.is_alive():
                    self.recvMsgThread = threading.Thread(target=self.recv_msg_from_eap,name="recv_msg_thread")
                    self.recvMsgThread.setDaemon(True)
                    self.recvMsgThread.start()

                if not self.sendMsgThread.is_alive():
                    self.sendMsgThread = threading.Thread(target=self.send_msg, name="send_msg_thread")
                    self.sendMsgThread.setDaemon(True)
                    self.sendMsgThread.start()

                if not self.askRequestThread.is_alive():
                    self.askRequestThread = threading.Thread(target=self.ask_eap_there_request, name="ask_request_thread")
                    self.askRequestThread.setDaemon(True)
                    self.askRequestThread.start()

                if not self.datetimeRequestThread.is_alive():
                    self.datetimeRequestThread = threading.Thread(target=self.date_time_report, name="date_time_report")
                    self.datetimeRequestThread.setDaemon(True)
                    self.datetimeRequestThread.start()

                if not self.totalCountReportThread.is_alive():
                    self.totalCountReportThread = threading.Thread(target=self.total_count_report, name='total_count_report')
                    self.totalCountReportThread.setDaemon(True)
                    self.totalCountReportThread.start()
            except Exception as e:
                print("Manger Error:{}".format(str(e)))

    def send_msg(self):
        pass


    def date_time_report(self):
        pass

    def total_count_report(self):
        pass


    def add_msg_to_msglist(self, msgtype, msgInfo):
        msgSend = []
        if msgtype == "recv":
            msgHead = "  从EAP接收到消息:"
        else:
            msgHead = "  发送消息到EAP:"
        msgHead = self.get_now_time_str() + msgHead
        msgSend.append(msgHead)
        msgBody = ["消息类型: {}".format(msgInfo[0]), "消息主体: {}".format(msgInfo[1]), "消息结果码: {}".format(msgInfo[2])]
        msgSend.append(msgBody)
        if len(self.msgList) >= 8:  # 如果消息的个数大于8条,就将最前面的一条删除.然后后面再加一个.
            self.msgList.pop(0)
        self.msgList.append(msgSend)
        print("msgList : {}".format(self.msgList))
        self.js.send_2_ui({"val": "updateEapMsg", "info": self.msgList})

    def ask_eap_there_request(self):
        """
        间断发送ask是否存在的消息,我初步设定是这样的,如果self.sock连接之后4秒以后开始发送ask,然后每隔60秒发送一次.
        :return:
        """
        while True and IS_MAIN_ALIVE:
            if self.sock is not None:
                msgName,data = self.get_ask_msg_data()
                res = self.send_msg_to_eap(msgName,data)
                if res:
                    data = self.sock.recv(BUFFER_SIZE)
                    if len(data) <= 0:
                        print("消息 {} 没有收到回复".format(msgName))
                    else:
                        msgNameRet,bodyDict,retCode = parse_recv_msg(data)
                        print("消息 {}收到了回复,回复的消息类型:{},内容:{},结果码:{}".format(msgName, msgNameRet, bodyDict, retCode))
                        self.add_msg_to_msglist("recv", [msgNameRet, bodyDict, retCode])
                    gevent.sleep(self.askDuration)
                    continue
                else:
                    gevent.sleep(0.5)
                    continue
            else:
                gevent.sleep(0.5)

    def get_ask_msg_data(self):
        """
        获取发送请求设备是否存在的请求的数据内容
        :return:
        """
        # 1. 首先是将要发送的消息写入到ask_msg
        msgName = "Are You There Request"
        bodyDict = {"user_id": self.deviceName,
                    "service_ip":self.deviceIp}
        retDict = {"returncode": '0',
                   "returnmessage": ''}
        pack_send_msg(msgName, bodyDict, retDict)

        # 2. 然后是读取文件发送这个消息
        with open(r'./ask_msg.xml', 'rb') as f:
            data = f.read()
        return msgName, data


    def recv_msg_from_eap(self):
        """
        接收消息的线程,处理接收到的消息.
        :return:
        """
        while True and IS_MAIN_ALIVE:
            if self.sock is not None and self.operationMode != "0":
                try:
                    recvData = self.sock.recv(BUFFER_SIZE)
                    if recvData:
                        msgName, bodyDict, retCode = parse_recv_msg(recvData)
                        print("从EAP接收到消息: msgName = {},bodyDict = {},retCode = {}".format(msgName, bodyDict, retCode))
                        self.add_msg_to_msglist("recv", [msgName, bodyDict, retCode])
                        if retCode == "0":
                            self.deal_recv_msg(msgName, bodyDict)
                        else:
                            print("从EAP接收到的消息,返回值为0")
                except Exception as e:
                    self.js.logger.debug("in recv_msg_from_eap error : {}".format(str(e)))
            else:
                gevent.sleep(0.5)

    def send_msg_to_eap(self, msgName, data):
        """
        向eap发送消息,注意发送消息的时候,要加上消息头和消息结尾
        :param data: xml消息的字节二进制格式
        :return:
        """
        msgSend = bytes(chr(2), encoding="ascii") + data + bytes(chr(3), encoding="ascii")
        print("发送消息到EAP: {}".format(msgName))  # 这里最好是解析出来消息的类型.也就是发送消息的名称,内容都解析出来.
        try:
            self.sock.sendall(msgSend)
        except Exception as e:
            print("in send_msg_to_eap(),self.sock.sendall() 出现异常: {}".format(str(e)))
            self.js.logger.debug("in send_msg_to_eap(),self.sock.sendall() 出现异常: {}".format(str(e)))
            return False
        return True

    def get_now_time_str(self):
        return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())

    def deal_recv_msg(self, msgType, bodyDict):
        """
        根据接收到的消息,进行消息的反馈
        :param msgType:
        :param bodyDict:
        :return:
        """
        if msgType == "Are You There Reply":
            self.eapReply = True  # 将EAP回复设置为True
        elif msgType == "Initial Data Request":
            # 1. 先获取要回复的数据,然后就是发送回复
            msgName, initialReplyData = self.get_initial_data_reply()
            self.send_msg_to_eap(msgName, initialReplyData)
        elif msgType == "Trace Data Request":
            # 设备收到点检数据请求
            msgName, traceRequestData = self.get_trace_reply_data()
            # 2. 点检数据恢复
            self.send_msg_to_eap(msgName, traceRequestData)
        elif msgType == "Date Time Sync Command":
            # 日期时间校时命令
            msgName, DateTimeSyncData = self.get_datetime_sync_data()
            # 回复收到时间校准命令,并进行时间矫正
            serviceTime = bodyDict["date_time"]
            self.sync_sys_time(serviceTime)
            self.send_msg_to_eap(msgName, DateTimeSyncData)
        elif msgType == "CIM Message Command":
            # 远程讯息命令,获取命令的种类,命令的时间间隔,消息的内容
            actionType = bodyDict["action_type"]  # 消息类别
            intervalSecondTime = bodyDict["interval_second_time"]  # 间隔秒数,间隔消息时使用
            cimMessage = bodyDict["cim_message"]  # 远程消息的内容
            print("收到远程讯息命令:,action_type = {},interval_second_time = {},cim_message = {}".format(actionType, intervalSecondTime, cimMessage))
            msgName, cimMessageReplyData = self.get_cim_message_reply_data()
            self.send_msg_to_eap(msgName, cimMessageReplyData)

        elif msgType == "Recipe Name Exist Request":
            # 询问配方是否存在
            recipeName = bodyDict["recipe_name"]
            data = self.db._get_data(self.db.params_db, "device_info")[1][0]
            recipeNameList = data.get("recipe_name_list", ["recipe_001", "recipe_002", "recipeName01"])

            if recipeName not in recipeNameList:
                recipeName = ""

            msgName, recipeExistReplyData = self.get_recipe_exist_reply_data(recipeName)
            self.send_msg_to_eap(msgName, recipeExistReplyData)

        elif msgType == "Recipe Body Request":
            # EAP以配方名称询问设备配方参数的详细内容
            recipeName = bodyDict["recipe_name"]
            data = self.db._get_data(self.db.params_db, "device_info")[1][0]
            recipeBodyTestData = [recipeName, [("width", "600"), ("height", "600")]]
            msgName, recipeBodyReplyData = self.get_recipe_body_reply_data(recipeBodyTestData)
            self.send_msg_to_eap(msgName, recipeBodyReplyData)
        elif msgType == "Recipe Name Change Request":
            # EAP 配方切换= 可以切换，不可以切换
            recipeName = bodyDict["recipe_name"]
            changeAllowed = False
            msgName, recipeChangeReplyData = self.get_recipe_change_reply_data(changeAllowed)
            self.send_msg_to_eap(msgName, recipeChangeReplyData)

    def get_initial_data_reply(self):
        """
        获取发送给初始化数据请求的二进制格式数据
        :return:
        """
        msgName = "Initial Data Reply"
        _, dataList = self.js.db._get_data(self.js.params_db, "device_info")
        data = dataList[0]
        totalWipCount = data.get("total_wip_count", 0)
        equipmentStatus = data.get("equipment_status", "1")
        recipeName = data.get("recipeName", "001")
        bodyDict = {"date_time": get_transaction_id()[:-3],
                    "operation_mode": self.operationMode,
                    "total_wip_count": totalWipCount,
                    "equipment_status": equipmentStatus,
                    "recipe_name": recipeName}
        retDict = {"returncode": '0',
                   "returnmessage": ''}
        pack_send_msg(msgName, bodyDict, retDict)
        with open(r"./send_msg.xml", "rb") as f:
            data = f.read()
        return msgName, data

    # 前端界面获取eap参数的函数
    def get_eap_params(self):
        try:
            collist = self.js.db.params_db.list_collection_names()
            if "eapParams" not in collist:
                return []
            else:
                res = list(self.js.db._get_data(self.js.db.params_db, "eapParams", {"key": "1"}, projection={"_id": 0, "time": 0})[1])
                if len(res[0].values()) == 1:
                    return []
                # 将eapParams重新赋值
                data = res[0]
                self.deviceName = data.get("deviceName", "HD_CBJ")
                self.eapIp = data.get("eapIp", "192.168.0.100")
                self.port = int(data.get('port', 5000))
                self.deviceIp = data.get("deviceIp","192.168.0.55")
                self.operationMode = data.get("operationMode", "0")
                self.askDuration = int(data.get("askDuration", "60"))
                self.currentTimeDuration = int(data.get("currentTimeDuration", "60"))
                self.totalCountDuration = int(data.get("totalCountDuration", "0"))
                return res
        except Exception as e:
            print(str(e))
            return []

    # 前端界面点击保存eap参数的函数
    def save_eap_params(self, eapParams):
        try:
            collist = self.js.db.params_db.list_collection_names()
            if "eapParams" not in collist:
                return []
            else:
                query = {"key": "1"}
                newValues = {"$set": eapParams}
                res = self.js.db.update_data(self.js.db.params_db, "eapParams", query, newValues)[0]
                return res
        except Exception as e:
            print(str(e))
            return str(e)

    # 界面上点击连接eap按钮,如果连接成功,返回"ok"
    def connect_eap(self):
        socket.setdefaulttimeout(self.timeout)
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.settimeout(self.timeout)
        self.modify_buffer_size(self.sock)
        try:
            self.sock.connect((self.eapIp, self.port))
            return "ok"
        except Exception as e:
            print("连接EAP失败,失败原因:{}".format(str(e)))
            self.sock = None  # 如果连接失败,将self.sock置为None
            return "连接超时,请查看网线是否插好以及EAP的IP地址是否正确?"

    def close_eap(self):
        if self.sock is not None:
            self.sock.close()
            self.sock = None
        return "ok"

    def modify_buffer_size(self, hdSock):
        # 获取当前套接字关联的选项,socket.SOL_SOCKET,正在使用的socket,socket.SOCK_STREAM发送缓存区的大小
        hdSock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)
        hdSock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF)

        # 设置TCP套接字关联的选项
        # socket.TCP_NODELAY TCP层套接口选项
        # socket.TCP_NODELAY.表示禁用了Nagle,允许小包发送.不用等很多的包折合一起再发送的问题.适用于数据量小的项目.
        hdSock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)

        # 设置发送缓存区套接字关联的选项
        hdSock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, BUFFER_SIZE)
        # 设置接收缓存区套接字关联的选项
        hdSock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, BUFFER_SIZE)
